package cn.iocoder.yudao.framework.pay.core.client.impl.huifu;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.pay.core.client.dto.huifu.UserBasicdataRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.huifu.UserBusiOpenRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.huifu.UserInfoReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferRespDTO;
import cn.iocoder.yudao.framework.pay.core.client.dto.transfer.PayTransferUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.enums.channel.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderDisplayModeEnum;
import cn.iocoder.yudao.framework.pay.core.enums.transfer.PayTransferTypeEnum;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.huifu.bspay.sdk.opps.client.BasePayClient;
import com.huifu.bspay.sdk.opps.core.BasePay;
import com.huifu.bspay.sdk.opps.core.request.*;
import com.huifu.bspay.sdk.opps.core.utils.DateTools;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;

import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN;
import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;

/**
 * 汇付微信下单
 */
@Slf4j
public class HuifuWxLitePayClient extends AbstractHuifuPayClient {

    // 在 cn.iocoder.yudao.framework.pay.core.client.impl.PayClientFactoryImpl——put引用
    public HuifuWxLitePayClient(Long channelId, HuifuWxPayClientConfig config) {
        super(channelId, PayChannelEnum.WX_LITE.getCode(), config);
    }

    /**
     * 初始化支付的配置
     */
    @Override
    protected void doInit() {
        super.doInit(Boolean.TRUE, BasePay.MODE_PROD);
    }

    /**
     * 统一下单（汇付 JSPay 接口）
     */
    @Override
    protected PayOrderRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws Throwable {
        // 1. 构建支付请求参数对象（V2TradePaymentJspayRequest 是汇付下单请求模型）
        V2TradePaymentJspayRequest v2TradePaymentJspayRequest = buildJsPayRequest(reqDTO);

        // 2. 发起请求，调用汇付接口，返回数据为 Map<String, Object>
        Map<String, Object> response = doExecute(v2TradePaymentJspayRequest);
        log.info("[汇付———微信小程序下单成功][结果参数({})]", response);

        if (!isOrderSuccess(response)) {
            log.warn("[汇付——微信小程序下单失败][结果参数({})]", response);
            return PayOrderRespDTO.closedOf(Convert.toStr(response.get("resp_code")),
                    Convert.toStr(response.get("resp_desc")), reqDTO.getOutTradeNo(), response);
        }

        // 3. 提取 pay_info 字段（内为支付配置参数，如 appId、prepayId 等）
        // 该字段本身是字符串形式的 JSON
        String payInfoJson = Convert.toStr(response.get("pay_info"));
        // 4. 转换 pay_info 为 JSONObject 再转为字符串，保障格式统一
        JSONObject payInfoObject = JSONObject.parseObject(payInfoJson);
        String payInfoStr = payInfoObject.toJSONString(); // 此处是返回给前端的 pay 配置参数字符串
        // 5. 构建统一返回 DTO（含：显示模式 + payInfo + 订单号 + 原始响应）
        return PayOrderRespDTO.waitingOf(
                PayOrderDisplayModeEnum.APP.getMode(), // 展示模式：APP 模式
                payInfoStr,                            // 前端需要使用的 pay 参数配置
                reqDTO.getOutTradeNo(),                // 外部交易单号
                response                               // 原始响应内容（可选传给前端或日志记录）
        );
    }

    private V2TradePaymentJspayRequest buildJsPayRequest(PayOrderUnifiedReqDTO reqDTO) {
        V2TradePaymentJspayRequest request = new V2TradePaymentJspayRequest();
        // 基础参数
        request.setReqDate(DateTools.getCurrentDateYYYYMMDD());// 请求日期
//        request.setReqSeqId(SequenceTools.getReqSeqId32());// 请求流水号
        request.setReqSeqId(reqDTO.getOutTradeNo());// 请求流水号
        request.setHuifuId(config.getHuifuId());// 商户号
        request.setTradeType("T_MINIAPP");// 交易类型
        request.setGoodsDesc(reqDTO.getBody());// 商品描述

        // 金额处理，保留2位小数
        String transAmt = BigDecimal.valueOf(reqDTO.getPrice())
                .divide(BigDecimal.valueOf(100))
                .setScale(2, RoundingMode.HALF_UP)
                .toString();
        request.setTransAmt(transAmt);

        // 扩展字段封装
        Map<String, Object> extendInfoMap = new HashMap<>();
//        extendInfoMap.put("time_expire", DateTools.formatYYYYMMDDHHSS(order.getExpireTime()));// 交易有效期
        extendInfoMap.put("limit_pay_type", "NO_CREDIT");// 禁用信用卡标记
        extendInfoMap.put("delay_acct_flag", "N"); // 是否延迟交易
        extendInfoMap.put("fq_mer_discount_flag", "N"); // 商户贴息标记
        extendInfoMap.put("notify_url", reqDTO.getNotifyUrl());// 异步通知地址
        extendInfoMap.put("remark", reqDTO.getBody()); // 备注

        // 构建 wx_data 对象
        JSONObject wxData = new JSONObject();
        wxData.put("sub_appid", config.getAppId());// 子商户公众账号id
        wxData.put("sub_openid", getOpenid(reqDTO));// 子商户用户标识
        extendInfoMap.put("wx_data", wxData.toJSONString());// 聚合正扫微信拓展参数集合

        request.setExtendInfo(extendInfoMap);
        return request;
    }

    /**
     * 解析汇付回调的支付订单通知
     *
     * @param params 回调参数 Map
     */
    @Override
    protected PayOrderRespDTO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
        // =================== 1. 校验网关返回码 ===================
        if (ObjUtil.notEqual(params.get("resp_code"), "00000000")) {
            throw new ServiceException(500, "汇支付网关返回码异常");
        }
        // =================== 2. 解析 resp_data ===================
        // resp_data 是一个 JSON 字符串，包含交易详情
        String respData = params.get("resp_data");
        if (StrUtil.isBlank(respData)) {
            throw new ServiceException(500, "回调参数缺少 resp_data 字段");
        }
        // 将 resp_data 转换为 JSONObject，便于后续取字段
        JSONObject response = JSONObject.parseObject(respData);

        // =================== 3. 提取关键字段 ===================
        String transStat = response.getString("trans_stat"); // S：成功、F：失败
        String reqSeqId = response.getString("req_seq_id"); // 请求流水号（我方生成，方便链路追踪）
        String outTransId = response.getString("out_trans_id"); // 与微信关联的交易单号
        // =================== 4. 解析微信侧返回 wx_response ===================
        String wxResponseStr = response.getString("wx_response");
        JSONObject wxResponseJson = JSON.parseObject(wxResponseStr);
        String openid = wxResponseJson.getString("openid"); // 获取 openid
        // =================== 5. 交易完成时间 ===================
        LocalDateTime successTime = parseDate(response.getString("end_time"));
        String respCode = response.getString("resp_code"); // 业务返回码
        String respDesc = response.getString("resp_desc"); // 业务返回描述
        // =================== 6. 根据交易状态构造返回结果 ===================
        if ("S".equals(transStat)) {
            // 成功：返回成功结果，携带微信 openid
            return PayOrderRespDTO.successOf(outTransId, openid, successTime, reqSeqId, params);
        } else {
            // 失败：返回关闭状态
            return PayOrderRespDTO.closedOf(respCode, respDesc, reqSeqId, params);
        }
    }

    /**
     * 获得支付订单信息
     * outTradeNo pay_order_extension订单号
     */
    @Override
    protected PayOrderRespDTO doGetOrder(String outTradeNo, LocalDateTime createTime) throws Throwable {
        // =================== 1. 构建请求 ===================
        V2TradePaymentScanpayQueryRequest request = new V2TradePaymentScanpayQueryRequest();
        request.setHuifuId(config.getHuifuId());  // 商户号
        String reqDate = TemporalAccessorUtil.format(createTime.atZone(ZoneId.systemDefault()), PURE_DATE_PATTERN);
        request.setOrgReqDate(reqDate); // 原机构请求日期
        request.setOrgReqSeqId(outTradeNo); // 服务订单创建请求流水号

        // =================== 2. 执行查询 ===================
        Map<String, Object> response = doExecute(request);
        log.info("[汇付——支付交易查询][结果参数({})]", response);

        // 接口失败，直接返回关闭结果
        if (!isSuccess(response)) {
            log.warn("[汇付——支付交易查询失败][outTradeNo = {}][resp_code = {}, resp_desc = {}]",
                    outTradeNo, response.get("resp_code"), response.get("resp_desc"));
            return PayOrderRespDTO.closedOf(
                    Convert.toStr(response.get("resp_code")),
                    Convert.toStr(response.get("resp_desc")),
                    outTradeNo, response
            );
        }

        // =================== 3. 提取关键字段 ===================
        String transStat = Convert.toStr(response.get("trans_stat")); // P：处理中；S：成功；F：失败
        String reqSeqId = Convert.toStr(response.get("org_req_seq_id")); // 原机构请求流水号（我方生成，方便链路追踪）
        String outTransId = Convert.toStr(response.get("out_trans_id")); // 与微信关联的交易单号
        String respCode = Convert.toStr(response.get("resp_code")); // 业务返回码
        String respDesc = Convert.toStr(response.get("resp_desc")); // 业务返回描述

        // =================== 4. 根据交易状态构造返回结果 ===================
        if ("S".equals(transStat)) {
            String wxResponseStr = Convert.toStr(response.get("wx_response"));
            JSONObject wxResponseJson = JSON.parseObject(wxResponseStr);
            String openid = wxResponseJson.getString("openid"); // 获取 openid
            LocalDateTime successTime = parseDate(Convert.toStr(response.get("end_time")));  // 交易完成时间
            // 成功：返回成功结果，携带微信 openid
            log.info("[汇付——支付交易查询成功][outTradeNo={}][outTransId={}][openid={}]", outTradeNo, outTransId, openid);
            return PayOrderRespDTO.successOf(outTransId, openid, successTime, reqSeqId, response);
        } else if ("P".equals(transStat)) {
            log.info("[汇付——支付交易查询处理中][outTradeNo={}][等待用户完成支付]", outTradeNo);
            return PayOrderRespDTO.waitingOf(PayOrderDisplayModeEnum.APP.getMode(), toJsonString(response),
                    reqSeqId, response);
        } else { // 失败：返回关闭状态
            log.warn("[汇付——支付交易查询失败][outTradeNo={}][respCode={} respDesc={}]", outTradeNo, respCode, respDesc);
            return PayOrderRespDTO.closedOf(respCode, respDesc, reqSeqId, response);
        }
    }

    static LocalDateTime parseDate(String time) {
        return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN);
    }

    // ===================================== 退 款 ====================================================
    @Override
    protected PayRefundRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
        // 1. 构建汇付退款请求对象
        V2TradePaymentScanpayRefundRequest request = new V2TradePaymentScanpayRefundRequest();
        request.setReqDate(DateTools.getCurrentDateYYYYMMDD()); // 请求日期
//        request.setReqSeqId(SequenceTools.getReqSeqId32()); // 请求流水号
        request.setReqSeqId(reqDTO.getOutRefundNo()); // 请求流水号
        request.setHuifuId(config.getHuifuId()); // 商户号
        request.setOrdAmt(convertPriceToYuan(reqDTO.getRefundPrice()));  // 申请退款金额，单位：元（需要将分转元）
        // 原交易请求日期（支付成功时间转 yyyyMMdd）
        LocalDateTime successTime = reqDTO.getSuccessTime();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        request.setOrgReqDate(successTime.format(formatter));

        // 2. 设置扩展参数
        Map<String, Object> extendInfoMap = new HashMap<>();
        // 原交易全局流水号（汇付返回的 org_hf_seq_id）
        extendInfoMap.put("org_hf_seq_id", reqDTO.getOrgHfSeqId());
        request.setExtendInfo(extendInfoMap);

        // 3. 发起 API 调用
        Map<String, Object> response = doExecute(request);
        log.info("[汇付-微信小程序退款][请求单号:{}] [返回结果:{}]", reqDTO.getOutRefundNo(), response);

        // 4. 校验是否调用成功
//        if (!isSuccess(response)) { // 交易中为 00000100
//            return PayRefundRespDTO.failureOf(Convert.toStr(response.get("resp_code")),
//                    Convert.toStr(response.get("resp_desc")), reqDTO.getOutRefundNo(), response);
//        }
        // 5. 解析返回结果
        String transStat = Convert.toStr(response.get("trans_stat")); // 交易状态：P=处理中，S=成功，F=失败
        String transFinishTime = Convert.toStr(response.get("trans_finish_time")); // 完成时间 yyyyMMddHHmmss
        String hfSeqId = Convert.toStr(response.get("hf_seq_id")); // 汇付全局流水号
        String reqSeqId = Convert.toStr(response.get("req_seq_id")); // 请求流水号(退款单号)
        // String wxResponseStr = Convert.toStr(response.get("wx_response"));
        // if (StrUtil.isNotBlank(wxResponseStr)) {
        //     JSONObject wxResponseJson = JSON.parseObject(wxResponseStr);
        //     String openid = wxResponseJson.getString("openid");
        //     log.debug("[汇付退款][openid={}]", openid);
        // }
        if ("S".equals(transStat)) { // 成功
            return PayRefundRespDTO.successOf(hfSeqId, parseDate(transFinishTime), reqSeqId, response);
        } else if ("P".equals(transStat)) { // 处理中
            return PayRefundRespDTO.waitingOf(hfSeqId, reqSeqId, response);
        } else { // F 或未知状态，认为失败
            return PayRefundRespDTO.failureOf(Convert.toStr(response.get("resp_code")),
                    Convert.toStr(response.get("resp_desc")), reqDTO.getOutRefundNo(), response);
        }
    }

    @Override
    protected PayRefundRespDTO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
        log.info("[汇付-微信小程序退款异步回调参数][返回结果:{}]", params);
        // ========== 1. 提取关键参数 ==========
        String respCode = params.get("resp_code");       // 网关响应码
        String respDesc = params.get("resp_desc");       // 响应描述
        String reqSeqId = params.get("req_seq_id");      // 请求流水号（退款单号）
        String hfSeqId = params.get("hf_seq_id");        // 汇付全局流水号
        String transStat = params.get("trans_stat");     // 交易状态：P=处理中，S=成功，F=失败
        String transFinishTime = params.get("trans_finish_time"); // 完成时间 yyyyMMddHHmmss
        // ========== 2. 校验网关返回码 ==========
        // 汇付规定：00000000 代表接口调用成功，其他代表失败
        if (!"00000000".equals(respCode)) {
            return PayRefundRespDTO.failureOf(respCode, respDesc, reqSeqId, params);
        }
        // ========== 3. 根据交易状态返回业务结果 ==========
        if ("S".equals(transStat)) { // 成功：返回成功的退款响应对象
            return PayRefundRespDTO.successOf(hfSeqId, parseDate(transFinishTime), reqSeqId, params);
        } else if ("P".equals(transStat)) {  // 处理中：返回等待中的退款响应对象
            return PayRefundRespDTO.waitingOf(hfSeqId, reqSeqId, params);
        } else { // F 或未知状态，认为失败
            return PayRefundRespDTO.failureOf(respCode, respDesc, reqSeqId, params);
        }
    }
    /**
     * 查询退款订单状态（调用汇付接口）
     */
    @Override
    protected PayRefundRespDTO doGetRefund(String outTradeNo, String outRefundNo, LocalDateTime reqTime) throws Throwable {
        // ================== 1. 组装查询请求参数 ==================
        V2TradePaymentScanpayRefundqueryRequest request = new V2TradePaymentScanpayRefundqueryRequest();
        request.setHuifuId(config.getHuifuId());  // 商户号
        // 退款请求日期，格式：yyyyMMdd
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
        request.setOrgReqDate(reqTime.format(formatter));
        // 退款全局流水号 退款请求流水号,退款全局流水号,终端订单号三选一不能都为空
//        request.setOrgHfSeqId("003100TOP2B221110093241P139ac139c0c00000");
        // 退款请求流水号
        request.setOrgReqSeqId(outRefundNo);

        // ================== 2. 发起 API 调用 ==================
        Map<String, Object> response = doExecute(request);
        log.info("[汇付-微信小程序退款订单查询][退款单号:{}][返回结果:{}]", outRefundNo, response);

        // ================== 3. 校验接口调用结果 ==================
        if (!isSuccess(response)) {
            return PayRefundRespDTO.failureOf(Convert.toStr(response.get("resp_code")),
                    Convert.toStr(response.get("resp_desc")), outRefundNo, response);
        }

        // ================== 4. 提取关键返回参数 ==================
        String respCode = Convert.toStr(response.get("resp_code"));       // 网关响应码
        String respDesc = Convert.toStr(response.get("resp_desc"));       // 响应描述
        String reqSeqId = Convert.toStr(response.get("org_req_seq_id"));      // 请求流水号（退款单号）
        String hfSeqId = Convert.toStr(response.get("org_hf_seq_id"));        // 汇付全局流水号
        String transStat = Convert.toStr(response.get("trans_stat"));     // 交易状态：P=处理中，S=成功，F=失败
        String transFinishTime = Convert.toStr(response.get("trans_finish_time")); // 完成时间 yyyyMMddHHmmss

        // ================== 5. 根据交易状态返回结果 ==================
        if ("S".equals(transStat)) { // 成功：返回成功的退款响应对象
            return PayRefundRespDTO.successOf(hfSeqId, parseDate(transFinishTime), reqSeqId, response);
        } else if ("P".equals(transStat)) {  // 处理中：返回等待中的退款响应对象
            return PayRefundRespDTO.waitingOf(hfSeqId, reqSeqId, response);
        } else { // F 或未知状态，认为失败
            return PayRefundRespDTO.failureOf(respCode, respDesc, reqSeqId, response);
        }
    }

    @Override
    protected PayTransferRespDTO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) throws Throwable {
        throw new UnsupportedOperationException("支付渠道的统一【转账】回调 未实现");
    }

    @Override
    protected PayTransferRespDTO doUnifiedTransfer(PayTransferUnifiedReqDTO reqDTO) throws Throwable {
        throw new UnsupportedOperationException("调用三方渠道发起转账 待实现");
    }

    @Override
    protected PayTransferRespDTO doGetTransfer(String outTradeNo, LocalDateTime createTime, String huifuId, PayTransferTypeEnum type) throws Throwable {
        throw new UnsupportedOperationException("获得转账订单信息 待实现");
    }

    @Override
    protected UserBasicdataRespDTO doCreateUserBasicdataIndv(UserInfoReqDTO userInfoReqDTO) throws Throwable {
        throw new UnsupportedOperationException("创建用户信息 待实现");
    }

    @Override
    protected UserBusiOpenRespDTO doUserBusiOpen(UserInfoReqDTO userInfoReqDTO) throws Throwable {
        throw new UnsupportedOperationException("用户业务入驻 待实现");
    }

    @Override
    protected UserBusiOpenRespDTO doUpdateuserBusiOpen(UserInfoReqDTO userInfoReqDTO) throws Throwable {
        return null;
    }

    /***
     * @Description: 执行API调用
     * @param request
     * @throws Exception  Map<String,Object>
     * @date: 2021年7月5日17:59:37
     */
    public static Map<String, Object> doExecute(BaseRequest request) throws Exception {
        return BasePayClient.request(request, false);
    }

    static String formatDateToYYYYMMDDHHMMSS(LocalDateTime time) {
        return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN);
    }

    static String formatDateToYYYYMMDD(LocalDateTime time) {
        return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATE_PATTERN);
    }

    private static String convertPriceToYuan(Integer price) {
        return BigDecimal.valueOf(price)
                .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP)
                .toPlainString();
    }

    /**
     * 判断接口返回是否成功（根据 resp_code）
     */
    private boolean isSuccess(Map<String, Object> response) {
        return "00000000".equals(Convert.toStr(response.get("resp_code")));
    }

    /**
     * 判断接口返回是否下单成功（根据 resp_code）
     */
    private boolean isOrderSuccess(Map<String, Object> response) {
        return "00000100".equals(Convert.toStr(response.get("resp_code")));
    }

    /**
     * 获取openid
     */
    static String getOpenid(PayOrderUnifiedReqDTO reqDTO) {
        String openid = MapUtil.getStr(reqDTO.getChannelExtras(), "openid");
        if (StrUtil.isEmpty(openid)) {
            throw invalidParamException("支付请求的 openid 不能为空！");
        }
        return openid;
    }
}
